package com.reger.l2cache.core;

import java.util.Iterator;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.util.SerializationUtils;

public class RedisCache extends Cache {

	private static final Logger log = LoggerFactory.getLogger(RedisCache.class);

	private static final String REDIS_CACHE_KEY_PREFIX = "cache.redis.l2.key:";
	private static final String REDIS_CACHE_KEY = REDIS_CACHE_KEY_PREFIX + "%s";
	private static final String REDIS_LOCK_KEY = "cache.redis.lock.keys:%s:lock";
	private static final byte[] EMPTY = new byte[] {};
	private final RedisConnectionFactory connectionFactory;

	public RedisCache(RedisConnectionFactory connectionFactory) {
		this(null, connectionFactory);
	}

	public RedisCache(Cache cache, RedisConnectionFactory connectionFactory) {
		super(cache);
		this.connectionFactory = connectionFactory;
	}

	private byte[] getLockKey(String cacheKey) {
		return String.format(REDIS_LOCK_KEY, cacheKey).getBytes();
	}

	private byte[] getCacheKey(String cacheKey) {
		return String.format(REDIS_CACHE_KEY, cacheKey).getBytes();
	}

	@Override
	protected boolean tryLock(String cacheKey, long timeOut) {
		log.debug("拿二级缓存{}锁", cacheKey);
		byte[] lockCacheKey = this.getLockKey(cacheKey);
		RedisConnection connection = null;
		try {
			connection = RedisConnectionUtils.getConnection(connectionFactory);
			if (connection.setNX(lockCacheKey, EMPTY)) {
				connection.expire(lockCacheKey, (long) Math.ceil(timeOut / 1000.0));
				return true;
			}
			return false;
		} finally {
			RedisConnectionUtils.releaseConnection(connection, connectionFactory);
		}
	}

	@Override
	protected void unLock(String cacheKey) {
		log.debug("释放二级缓存{}锁", cacheKey);
		RedisConnection connection = null;
		try {
			connection = RedisConnectionUtils.getConnection(connectionFactory);
			connection.del(this.getLockKey(cacheKey));
		} finally {
			RedisConnectionUtils.releaseConnection(connection, connectionFactory);
		}
	}

	@Override
	protected synchronized boolean exist(String cacheKey) {
		log.debug("从二级缓存判断{}结果集", cacheKey);

		RedisConnection connection = null;
		try {
			connection = RedisConnectionUtils.getConnection(connectionFactory);
			return connection.exists(this.getCacheKey(cacheKey));
		} finally {
			RedisConnectionUtils.releaseConnection(connection, connectionFactory);
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	protected Relust<Object> get(String cacheKey, Class<?> returnType) {
		log.debug("从二级缓存查找{}结果集", cacheKey);

		RedisConnection connection = null;
		try {
			connection = RedisConnectionUtils.getConnection(connectionFactory);
			byte[] bytes = connection.get(this.getCacheKey(cacheKey));
			return (Relust<Object>) SerializationUtils.deserialize(bytes);
		} finally {
			RedisConnectionUtils.releaseConnection(connection, connectionFactory);
		}
	}

	@Override
	protected void put(String cacheKey, Object value, long timeOut) {

		RedisConnection connection = null;
		try {
			connection = RedisConnectionUtils.getConnection(connectionFactory);
			if (value == null) {
				connection.setEx(this.getCacheKey(cacheKey), (long) Math.ceil(timeOut / 1000.0), Relust.NULL_BYTE);
			} else {
				connection.setEx(this.getCacheKey(cacheKey), (long) Math.ceil(timeOut / 1000.0),
						SerializationUtils.serialize(new Relust<Object>(value)));
			}
			log.debug("写二级缓存{}到结果集", cacheKey);
			connection.publish(Cache.L2CACHE_SYNC_PUT_CHANNEL.getBytes(), cacheKey.getBytes());
		} finally {
			RedisConnectionUtils.releaseConnection(connection, connectionFactory);
		}
	}

	@Override
	protected void putException(String cacheKey, Throwable e, long timeOut) {
		RedisConnection connection = null;
		try {
			connection = RedisConnectionUtils.getConnection(connectionFactory);
			connection.publish(Cache.L2CACHE_SYNC_PUT_CHANNEL.getBytes(), cacheKey.getBytes());
		} finally {
			RedisConnectionUtils.releaseConnection(connection, connectionFactory);
		}
	}

	@Override
	protected void remove(String cacheKey) {

		RedisConnection connection = null;
		try {
			connection = RedisConnectionUtils.getConnection(connectionFactory);
			connection.del(this.getCacheKey(cacheKey));
			log.debug("从二级缓存删除{}结果集", cacheKey);
			connection.publish(Cache.L2CACHE_SYNC_DEL_CHANNEL.getBytes(), cacheKey.getBytes());
		} finally {
			RedisConnectionUtils.releaseConnection(connection, connectionFactory);
		}
	}

	@Override
	protected void clear() {

		RedisConnection connection = null;
		try {
			connection = RedisConnectionUtils.getConnection(connectionFactory);
			Set<byte[]> keys = connection.keys((REDIS_CACHE_KEY_PREFIX + "*").getBytes());
			Long num = connection.del(keys.toArray(new byte[][] {}));
			log.info("成功清理{}个缓存·", num);

			Iterator<byte[]> it = keys.iterator();
			while (it.hasNext()) {
				byte[] cacheKey = (byte[]) it.next();
				connection.publish(Cache.L2CACHE_SYNC_DEL_CHANNEL.getBytes(), cacheKey);
			}
		} finally {
			RedisConnectionUtils.releaseConnection(connection, connectionFactory);
		}
	}

}
